/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;

using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Utils;

namespace Microsoft.Scripting.Ast {
    
    /// <summary>
    /// Walks the DLR tree and reduces dynamic AST nodes
    /// TODO: copy into Microsoft.Scripting, make internal
    /// </summary>
    public class DynamicNodeRewriter : TreeRewriter {
        protected override Expression Rewrite(ActionExpression node) {
            Debug.Assert(node.IsDynamic);

            return RewriteSite(node.BindingInfo, node.Type, node.Arguments);
        }

        protected override Expression Rewrite(BinaryExpression node) {
            if (node.IsDynamic) {
                return RewriteSite(node.BindingInfo, node.Type, node.Left, node.Right);
            }

            return base.Rewrite(node);
        }

        protected override Expression Rewrite(UnaryExpression node) {
            if (node.IsDynamic) {
                return RewriteSite(node.BindingInfo, node.Type, node.Operand);
            }

            return base.Rewrite(node);
        }

        protected override Expression Rewrite(InvocationExpression node) {
            if (node.IsDynamic) {
                return RewriteSite(node.BindingInfo, node.Type, ArrayUtils.Insert(node.Expression, node.Arguments));
            }

            return base.Rewrite(node);
        }

        protected override Expression Rewrite(MethodCallExpression node) {
            if (node.IsDynamic) {
                return RewriteSite(node.BindingInfo, node.Type, node.Arguments);
            }

            return base.Rewrite(node);
        }

        protected override Expression Rewrite(NewExpression node) {
            if (node.IsDynamic) {
                return RewriteSite(node.BindingInfo, node.Type, node.Arguments);
            }

            return base.Rewrite(node);
        }

        protected override Expression Rewrite(DeleteExpression node) {
            Debug.Assert(node.IsDynamic);
            return RewriteSite(node.BindingInfo, node.Type, node.Expression);
        }

        protected override Expression Rewrite(AssignmentExpression node) {
            if (node.IsDynamic) {
                switch (node.Expression.NodeType) {
                    case ExpressionType.ArrayIndex:
                        BinaryExpression arrayIndex = (BinaryExpression)node.Expression;
                        return RewriteSite(node.BindingInfo, node.Type, arrayIndex.Left, arrayIndex.Right, node.Value);
                    case ExpressionType.MemberAccess:
                        return RewriteSite(node.BindingInfo, node.Type, ((MemberExpression)node.Expression).Expression, node.Value);
                }
            }

            return base.Rewrite(node);
        }

        protected override Expression Rewrite(MemberExpression node) {
            if (node.IsDynamic) {
                return RewriteSite(node.BindingInfo, node.Type, node.Expression);
            }

            return base.Rewrite(node);
        }

        private Expression RewriteSite(CallSiteBinder bindingInfo, Type retType, params Expression[] args) {
            return RewriteSite(bindingInfo, retType, (IList<Expression>)args);
        }

        private Expression RewriteSite(CallSiteBinder bindingInfo, Type retType, IList<Expression> args) {
            Type siteType = DynamicSiteHelpers.MakeDynamicSiteType(CompilerHelpers.GetSiteTypes(args, retType));

            // Rewrite the site as a constant
            Expression siteExpr = Expression.Constant(DynamicSiteHelpers.MakeSite(bindingInfo, siteType));

            // Rewrite all of the arguments first
            Expression[] siteArgs = new Expression[args.Count + 1];
            siteArgs[0] = RewriteNode(siteExpr);
            for (int i = 0; i < args.Count; i++) {
                siteArgs[i + 1] = RewriteNode(args[i]);
            }

            PropertyInfo targetProp = siteType.GetProperty("Target");

            // site.Target.Invoke(site, *args)
            return Expression.Call(
                Expression.Property(siteExpr, targetProp),
                targetProp.PropertyType.GetMethod("Invoke"),
                siteArgs
            );
        }        
    }
}
